iT邦幫忙

2

[APP] [Flutter]Google play訂閱 + firebase firestore建立訂閱系統

  • 分享至 

  • xImage
  •  

不知道你有沒有遇到這個問題:我是個獨立APP開發者,我想要開發一個APP,我會做會員系統,還需要一個後台去儲存用戶的資料,同時我又想做訂閱的機制。

我就很常遇到這個問題,在於後台的部分我最常使用的就是Firebase,使用Authentication + Cloud Firestore就可以簡單得建立一個會員系統了,這邊就不再贅述。

然而,長期有一個問題困擾著我,我該怎麼做訂閱的機制?

在簡單的研究後我了解到通常在做訂閱系統時會需要一個後台,並且這個後台要與Google play做通訊,確保後台的資料隨時更新到最新的訂閱狀態。

可惜當時的我只研究到這裡,怎麼去做通訊就完全不知道了,因此我試了很多替代方案,例如:用google api隨時去抓取最新的訂閱狀態(但這樣訂閱綁定的是google play帳號,而不是用戶在我APP登入的帳號)、改用單次購買的商品,購買後去更新後台訂閱狀態(可惜這樣無法自動續訂)

直到最近才下定決心去研究訂閱系統的製作,一樣是用Firebase做為後台,接下來會介紹最關鍵的部分:Google play要如何跟Firebase達成通訊

(先打個預防針,訂閱的某些機制我現在也還不是很了解,這邊就是紀錄我最終的作法,歡迎大家提供意見~)

首先,假設你已經寫好一個APP,而且在Google play console也已經建立一個APP

建立訂閱項目

那麼第一步就是先建立新的訂閱,在google play console進入你的APP點擊左側的訂閱,再點擊右上角的"建立訂閱項目"
https://ithelp.ithome.com.tw/upload/images/20240201/20136380VCICj0KxNB.png

建立新的訂閱後你還需要在訂閱裡去新增基礎方案(我的APP是用flutter去寫,然而在IAP(In App Purchase)購買時不太清楚有沒有辦法一個訂閱有多個方案並且買特定的方案,所以我都是每個訂閱裡包含一個方案,看有沒有大神可以解答)
https://ithelp.ithome.com.tw/upload/images/20240201/20136380ptTgQU6iRO.png

啟用Pub/Sub

接著,你需要到google cloud platform去啟用Pub/Sub
(https://console.cloud.google.com/apis/library/pubsub.googleapis.com)
https://ithelp.ithome.com.tw/upload/images/20240201/20136380GpHTydBZEZ.png

到Pub/Sub的頁面,點選左上"建立主題"
https://ithelp.ithome.com.tw/upload/images/20240201/20136380SMO0TYuL6n.png

輸入主題ID然後建立
https://ithelp.ithome.com.tw/upload/images/20240201/20136380GGS8BusMTu.png

在你新增的主題右邊點選"新增主體"
https://ithelp.ithome.com.tw/upload/images/20240201/20136380EZ0xnDTmSg.png

新增主題內貼上 google-play-developer-notifications@system.gserviceaccount.com ,角色則是選擇Pub/Sub底下的"發布/訂閱發布者"https://ithelp.ithome.com.tw/upload/images/20240201/20136380kKc07vlyIP.png

複製"主題名稱"
https://ithelp.ithome.com.tw/upload/images/20240201/20136380Os2YBhdXq8.png

回到google play console左側的"營利設定",把剛剛複製的主題名稱貼上
https://ithelp.ithome.com.tw/upload/images/20240201/20136380k1zwbUvH6C.png

以上是參考 https://yunghsincc.medium.com/%E4%BD%BF%E7%94%A8ktor%E5%AF%A6%E4%BD%9Candroid%E8%A8%82%E9%96%B1-part4-8bbb16703857

到這邊google方面已經完成設定了,再來要設定firestore了

Firebase Functions

首先要先啟用firebase的Blaze方案並啟用functions
https://ithelp.ithome.com.tw/upload/images/20240201/20136380pofOSVgd3l.png
https://ithelp.ithome.com.tw/upload/images/20240201/20136380yOmzxZfHjG.png

啟用functions會有一些簡單的指示告訴你怎麼做
https://ithelp.ithome.com.tw/upload/images/20240201/201363801F8Q95uydn.png

詳細的部屬可以參考官方文件或以下資源:
https://firebase.google.com/docs/functions/get-started?hl=zh-tw&gen=2nd
https://www.letswrite.tw/cloud-functions-init/
https://hackmd.io/@phzeng/r1ahu0cer#%E4%B8%80%E3%80%81%E4%BB%80%E9%BA%BC%E6%98%AFCloud-Function%EF%BC%9F

這邊只簡單介紹一下Firebase functions(好吧,只是我懶),基本上functions是在你的本地端做撰寫,寫完了之後再佈署到firebase,所以這些教學都是教你怎麼在本地init專案及佈署到firebase

當你已經學會佈署functions了,接下來你需要做的是要寫一個function去接收來自google的訂閱通知,基本上firebase已經有提供了必要的package,只要導入就可以輕易地使用了,而我這邊是請Chat-gpt幫忙產出程式碼再自己做微調

在開始寫function之前我們需要先了解一下google 打過來的資料是甚麼格式,下面是一個範例:

{
  "version":"1.0",
  "packageName":"com.some.thing",
  "eventTimeMillis":"1503349566168",
  "subscriptionNotification":
  {
    "version":"1.0",
    "notificationType":4,
    "purchaseToken":"PURCHASE_TOKEN",
    "subscriptionId":"monthly001"
  }
}

簡單介紹幾個重要的參數:
packageName: APP的套件名稱
eventTimeMillis:通知的觸發時間
notificationType:訂閱狀態
https://ithelp.ithome.com.tw/upload/images/20240201/20136380ceUBfqXrDV.png
purchaseToken:訂閱購買時產出的token,除非是新購買不然這個token不會變(可以用這組token找到訂閱者)
subscriptionId:訂閱項目ID(可以知道這個訂閱是哪個訂閱產品)
參考: https://developer.android.com/google/play/billing/rtdn-reference?hl=zh-cn

知道了google的回傳格式我們就可以來寫function了,這邊提供我寫用來接收訂閱資訊的Function:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const { PubSub, Snapshot } = require('@google-cloud/pubsub');

const pubSubClient = new PubSub();
admin.initializeApp();

//topic內放前面Pub/Sub設定的主題ID
exports.processRenewalNotifications = functions.pubsub.topic('subscription').onPublish((message) => {
    try {
        // 解碼 Pub/Sub 消息
        const messageData = message.data ? Buffer.from(message.data, 'base64').toString() : null;
        const data = JSON.parse(messageData);
        

        //用purchaseToken去找出是哪個用戶的訂閱資訊,並判斷是哪個訂閱產品,決定要延長的訂閱時間
        const db = admin.firestore();
        db.collection('users')
            .where('purchaseToken', '==', data.subscriptionNotification.purchaseToken)
            .get()
            .then((snapshot) => {
                if (snapshot.empty) {
                    return;
                }

                const productId = data.subscriptionNotification.subscriptionId;
                const timeMills = data.eventTimeMillis;

                const currentDate = new Date(parseInt(timeMills));

                switch (productId) {
                    case '1month_sub':
                        currentDate.setMonth(currentDate.getMonth() + 1);
                        break;
                    case '6month_sub':
                        currentDate.setMonth(currentDate.getMonth() + 6);
                        break;
                    case '1year_sub':
                        currentDate.setFullYear(currentDate.getFullYear() + 1);
                        break;

                }

                // 設定訂閱週期的隔天0時為下個到期日
                currentDate.setDate(currentDate.getDate() + 1);
                currentDate.setHours(0, 0, 0, 0);

                const newTimestamp = Math.floor(currentDate.getTime());

                const type = data.subscriptionNotification.notificationType;


                // 去更新用戶的訂閱狀態,我是設定只有notificationType是2的時候才去更新訂閱時間
                snapshot.forEach(doc => {
                    db.collection('users').doc(doc.id).update({
                        'productId': data.subscriptionNotification.subscriptionId,
                        'notificationType': data.subscriptionNotification.notificationType,
                        ...(type === 2 ? { 'expire_time': newTimestamp } : {})
                    });

                   // 把整筆訂閱資訊記錄下來,有爭議時有個依據
                   db.collection('users')
                     .doc(doc.id)
                     .collection('iap')
                     .doc(timeMills.toString())
                     .set(data);
                });

            })
            .catch((error) => { });

        return null;
    } catch (error) {
        console.error('Error processing renewal notification:', error);
        throw new functions.https.HttpsError('unknown', 'Failed to process renewal notification', error);
    }
});

到這邊就已經完成囉,可以到google play console的"營利設定"點擊傳送測試訊息看有沒有收到喔
https://ithelp.ithome.com.tw/upload/images/20240201/20136380H9ZeakgHuj.png

最後簡單總結一下邏輯:
購買訂閱:
消費者下單 => google play => 回傳APP下單成功 => APP通知firebase後台下單成功並儲存purchaseToken

訂閱狀態發生改變時(EX:自動續訂):
google play透過Pub/Sub發出通知 => 利用firebase function接收通知 => 用purchaseToken找出用戶並修改訂閱狀態

以上就是訂閱的流程,原本覺得很複雜,但寫下來發現其實也蠻簡單的,另外距離我做出訂閱流程已經過了一段時間了,可能會有些缺漏也不是那麼詳細,還請見諒

最後提一下,我不確定這是不是最佳的訂閱處理流程,但基本邏輯應該沒有問題,若有錯誤的地方也歡迎糾正


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言